/*-------------------------------------------------------------------------
|   RXTX License v 2.1 - LGPL v 2.1 + Linking Over Controlled Interface.
|   RXTX is a native interface to serial ports in java.
|   Copyright 1997-2009 by Trent Jarvi tjarvi@qbang.org and others who
|   actually wrote it.  See individual source files for more information.
|
|   A copy of the LGPL v 2.1 may be found at
|   http://www.gnu.org/licenses/lgpl.txt on March 4th 2007.  A copy is
|   here for your convenience.
|
|   This library is free software; you can redistribute it and/or
|   modify it under the terms of the GNU Lesser General Public
|   License as published by the Free Software Foundation; either
|   version 2.1 of the License, or (at your option) any later version.
|
|   This library is distributed in the hope that it will be useful,
|   but WITHOUT ANY WARRANTY; without even the implied warranty of
|   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
|   Lesser General Public License for more details.
|
|   An executable that contains no derivative of any portion of RXTX, but
|   is designed to work with RXTX by being dynamically linked with it,
|   is considered a "work that uses the Library" subject to the terms and
|   conditions of the GNU Lesser General Public License.
|
|   The following has been added to the RXTX License to remove
|   any confusion about linking to RXTX.   We want to allow in part what
|   section 5, paragraph 2 of the LGPL does not permit in the special
|   case of linking over a controlled interface.  The intent is to add a
|   Java Specification Request or standards body defined interface in the
|   future as another exception but one is not currently available.
|
|   http://www.fsf.org/licenses/gpl-faq.html#LinkingOverControlledInterface
|
|   As a special exception, the copyright holders of RXTX give you
|   permission to link RXTX with independent modules that communicate with
|   RXTX solely through the Sun Microsytems CommAPI interface version 2,
|   regardless of the license terms of these independent modules, and to copy
|   and distribute the resulting combined work under terms of your choice,
|   provided that every copy of the combined work is accompanied by a complete
|   copy of the source code of RXTX (the version of RXTX used to produce the
|   combined work), being distributed under the terms of the GNU Lesser General
|   Public License plus this exception.  An independent module is a
|   module which is not derived from or based on RXTX.
|
|   Note that people who make modified versions of RXTX are not obligated
|   to grant this special exception for their modified versions; it is
|   their choice whether to do so.  The GNU Lesser General Public License
|   gives permission to release a modified version without this exception; this
|   exception also makes it possible to release a modified version which
|   carries forward this exception.
|
|   You should have received a copy of the GNU Lesser General Public
|   License along with this library; if not, write to the Free
|   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
|   All trademarks belong to their respective owners.
--------------------------------------------------------------------------*/
package  gnu.io;

import  java.io.FileDescriptor;
import java.util.HashMap;
import  java.util.Vector;
import  java.util.Enumeration;

/**
* @author Trent Jarvi
* @version %I%, %G%
* @since JDK1.0
*/

public class CommPortIdentifier extends Object /* extends Vector? */
{
	public static final int PORT_SERIAL   = 1;  // rs232 Port
	public static final int PORT_PARALLEL = 2;  // Parallel Port
	public static final int PORT_I2C      = 3;  // i2c Port
	public static final int PORT_RS485    = 4;  // rs485 Port
	public static final int PORT_RAW      = 5;  // Raw Port
	private String PortName;
	private boolean Available = true;
	private String Owner;
	private CommPort commport;
	private CommDriver RXTXDriver;
 	static CommPortIdentifier   CommPortIndex;
	CommPortIdentifier next;
	private int PortType;
	private final static boolean debug = false;
	static Object Sync;
	Vector ownershipListener;



/*------------------------------------------------------------------------------
	static {}   aka initialization
	accept:       -
	perform:      load the rxtx driver
	return:       -
	exceptions:   Throwable
	comments:     static block to initialize the class
------------------------------------------------------------------------------*/
	// initialization only done once....
	static
	{
		if(debug) System.out.println("CommPortIdentifier:static initialization()");
		Sync = new Object();
		try
		{
			CommDriver RXTXDriver = (CommDriver) Class.forName("gnu.io.RXTXCommDriver").newInstance();
			RXTXDriver.initialize();
		}
		catch (Throwable e)
		{
			System.err.println(e + " thrown while loading " + "gnu.io.RXTXCommDriver");
		}

		String OS;

		OS = System.getProperty("os.name");
		if(OS.toLowerCase().indexOf("linux") == -1)
		{
			if (debug)
				System.out.println("Have not implemented native_psmisc_report_owner(PortName)); in CommPortIdentifier");
		}
		// Do not load the library here, but rely on the loading in RXTXVersion.
	}
	CommPortIdentifier ( String pn, CommPort cp, int pt, CommDriver driver)
	{
		PortName        = pn;
		commport        = cp;
		PortType        = pt;
		next            = null;
		RXTXDriver      = driver;

	}

/*------------------------------------------------------------------------------
	addPortName()
	accept:         Name of the port s, Port type,
                        reverence to RXTXCommDriver.
	perform:        place a new CommPortIdentifier in the linked list
	return: 	none.
	exceptions:     none.
	comments:
------------------------------------------------------------------------------*/
	public static void addPortName(String s, int type, CommDriver c)
	{

		if(debug) System.out.println("CommPortIdentifier:addPortName("+s+")");
		AddIdentifierToList(new CommPortIdentifier(s, null, type, c));
	}
/*------------------------------------------------------------------------------
	AddIdentifierToList()
	accept:        The cpi to add to the list.
	perform:
	return:
	exceptions:
	comments:
------------------------------------------------------------------------------*/
	private static void AddIdentifierToList( CommPortIdentifier cpi)
	{
		if(debug) System.out.println("CommPortIdentifier:AddIdentifierToList()");
		synchronized (Sync)
		{
			if (CommPortIndex == null)
			{
				CommPortIndex = cpi;
				if(debug) System.out.println("CommPortIdentifier:AddIdentifierToList() null");
			}
			else
			{
				CommPortIdentifier index  = CommPortIndex;
				while (index.next != null)
				{
					index = index.next;
					if(debug) System.out.println("CommPortIdentifier:AddIdentifierToList() index.next");
				}
				index.next = cpi;
			}
		}
	}
/*------------------------------------------------------------------------------
	addPortOwnershipListener()
	accept:
	perform:
	return:
	exceptions:
	comments:
------------------------------------------------------------------------------*/
	public void addPortOwnershipListener(CommPortOwnershipListener c)
	{
		if(debug) System.out.println("CommPortIdentifier:addPortOwnershipListener()");

		/*  is the Vector instantiated? */

		if( ownershipListener == null )
		{
			ownershipListener = new Vector();
		}

		/* is the ownership listener already in the list? */

		if ( ownershipListener.contains(c) == false)
		{
			ownershipListener.addElement(c);
		}
	}
/*------------------------------------------------------------------------------
	getCurrentOwner()
	accept:
	perform:
	return:
	exceptions:
	comments:
------------------------------------------------------------------------------*/
	public String getCurrentOwner()
	{
		if(debug) System.out.println("CommPortIdentifier:getCurrentOwner()");
		return( Owner );
	}
/*------------------------------------------------------------------------------
	getName()
	accept:
	perform:
	return:
	exceptions:
	comments:
------------------------------------------------------------------------------*/
	public String getName()
	{
		if(debug) System.out.println("CommPortIdentifier:getName()");
		return( PortName );
	}
/*------------------------------------------------------------------------------
	getPortIdentifier()
	accept:
	perform:
	return:
	exceptions:
	comments:
------------------------------------------------------------------------------*/
	static public CommPortIdentifier getPortIdentifier(String s) throws NoSuchPortException
	{
		if(debug) System.out.println("CommPortIdentifier:getPortIdentifier(" + s +")");
		CommPortIdentifier index;

		synchronized (Sync)
		{
		 	index = CommPortIndex;
			while (index != null && !index.PortName.equals(s)) {
				index = index.next;
			}
			if (index == null) {
				/* This may slow things down but if you pass the string for the port after
				   a device is plugged in, you can find it now.

				   http://bugzilla.qbang.org/show_bug.cgi?id=48
				*/
				getPortIdentifiers();
			 	index = CommPortIndex;
				while (index != null && !index.PortName.equals(s)) {
					index = index.next;
				}
			}
		}
		if (index != null) return index;
		else
		{
			if ( debug )
				System.out.println("not found!" + s);
			throw new NoSuchPortException();
		}
	}
/*------------------------------------------------------------------------------
	getPortIdentifier()
	accept:
	perform:
	return:
	exceptions:
	comments:
------------------------------------------------------------------------------*/
	static public CommPortIdentifier getPortIdentifier(CommPort p)
		throws NoSuchPortException
	{
		if(debug) System.out.println("CommPortIdentifier:getPortIdentifier(CommPort)");
		CommPortIdentifier c;
		synchronized( Sync )
		{
			c = CommPortIndex;
			while ( c != null && c.commport != p )
				c = c.next;
		}
		if ( c != null )
			return (c);

		if ( debug )
			System.out.println("not found!" + p.getName());
		throw new NoSuchPortException();
	}
/*------------------------------------------------------------------------------
	getPortIdentifiers()
	accept:
	perform:
	return:
	exceptions:
	comments:
------------------------------------------------------------------------------*/
	static public Enumeration getPortIdentifiers()
	{
		if(debug) System.out.println("static CommPortIdentifier:getPortIdentifiers()");
		//Do not allow anybody get any ports while we are re-initializing
		//because the CommPortIndex points to invalid instances during that time
		synchronized(Sync) {
			//Remember old ports in order to restore them for ownership events later
			HashMap oldPorts = new HashMap();
			CommPortIdentifier p = CommPortIndex;
			while(p!=null) {
				oldPorts.put(p.PortName, p);
				p = p.next;
			}
			CommPortIndex = null;
			try
			{
				//Initialize RXTX: This leads to detecting all ports
				//and writing them into our CommPortIndex through our method
				//{@link #addPortName(java.lang.String, int, gnu.io.CommDriver)}
				//This works while lock on Sync is held
				CommDriver RXTXDriver = (CommDriver) Class.forName("gnu.io.RXTXCommDriver").newInstance();
				RXTXDriver.initialize();
				//Restore old CommPortIdentifier objects where possible,
				//in order to support proper ownership event handling.
				//Clients might still have references to old identifiers!
				CommPortIdentifier curPort = CommPortIndex;
				CommPortIdentifier prevPort = null;
				while(curPort!=null) {
					CommPortIdentifier matchingOldPort = (CommPortIdentifier)oldPorts.get(curPort.PortName);
					if(matchingOldPort!=null && matchingOldPort.PortType == curPort.PortType) {
						//replace new port by old one
						matchingOldPort.RXTXDriver = curPort.RXTXDriver;
						matchingOldPort.next = curPort.next;
						if(prevPort==null) {
							CommPortIndex = matchingOldPort;
						} else {
							prevPort.next = matchingOldPort;
						}
						prevPort = matchingOldPort;
					} else {
						prevPort = curPort;
					}
					curPort = curPort.next;
				}
			}
			catch (Throwable e)
			{
				System.err.println(e + " thrown while loading " + "gnu.io.RXTXCommDriver");
				System.err.flush();
			}
		}
		return new CommPortEnumerator();
	}
/*------------------------------------------------------------------------------
	getPortType()
	accept:
	perform:
	return:
	exceptions:
	comments:
------------------------------------------------------------------------------*/
	public int getPortType()
	{
		if(debug) System.out.println("CommPortIdentifier:getPortType()");
		return( PortType );
	}
/*------------------------------------------------------------------------------
	isCurrentlyOwned()
	accept:
	perform:
	return:
	exceptions:
	comments:
------------------------------------------------------------------------------*/
	public synchronized boolean isCurrentlyOwned()
	{
		if(debug) System.out.println("CommPortIdentifier:isCurrentlyOwned()");
		return(!Available);
	}
/*------------------------------------------------------------------------------
	open()
	accept:
	perform:
	return:
	exceptions:
	comments:
------------------------------------------------------------------------------*/
	public synchronized CommPort open(FileDescriptor f) throws UnsupportedCommOperationException
	{
		if(debug) System.out.println("CommPortIdentifier:open(FileDescriptor)");
		throw new UnsupportedCommOperationException();
	}
	private native String native_psmisc_report_owner(String PortName);

/*------------------------------------------------------------------------------
	open()
	accept:      application making the call and milliseconds to block
                     during open.
	perform:     open the port if possible
	return:      CommPort if successful
	exceptions:  PortInUseException if in use.
	comments:
------------------------------------------------------------------------------*/
	private boolean HideOwnerEvents;

	public CommPort open(String TheOwner, int i)
		throws gnu.io.PortInUseException
	{
		if(debug) System.out.println("CommPortIdentifier:open("+TheOwner + ", " +i+")");
		boolean isAvailable;
		synchronized(this) {
			isAvailable = this.Available;
			if (isAvailable) {
			    //assume ownership inside the synchronized block
			    this.Available = false;
			    this.Owner = TheOwner;
			}
		}
		if (!isAvailable)
		{
			long waitTimeEnd = System.currentTimeMillis() + i;
			//fire the ownership event outside the synchronized block
			fireOwnershipEvent(CommPortOwnershipListener.PORT_OWNERSHIP_REQUESTED);
			long waitTimeCurr;
			synchronized(this) {
				while(!Available && (waitTimeCurr=System.currentTimeMillis()) < waitTimeEnd) {
					try
					{
						wait(waitTimeEnd - waitTimeCurr);
					}
					catch ( InterruptedException e )
					{
						Thread.currentThread().interrupt();
						break;
					}
				}
				isAvailable = this.Available;
				if (isAvailable) {
					//assume ownership inside the synchronized block
					this.Available = false;
					this.Owner = TheOwner;
				}
			}
		}
		if (!isAvailable)
		{
			throw new gnu.io.PortInUseException(getCurrentOwner());
		}
		//At this point, the CommPortIdentifier is owned by us.
		try {
			if(commport == null)
			{
				commport = RXTXDriver.getCommPort(PortName,PortType);
			}
			if(commport != null)
			{
				fireOwnershipEvent(CommPortOwnershipListener.PORT_OWNED);
				return commport;
			}
			else
			{
				String err_msg;
				try {
					err_msg = native_psmisc_report_owner(PortName);
				} catch (Throwable t)
				{
					err_msg = "Port " + PortName + " already owned... unable to open.";
				}
				throw new gnu.io.PortInUseException( err_msg );
			}
		} finally {
			if(commport == null) {
				//something went wrong reserving the commport -> unown the port
				synchronized(this) {
					this.Available = true;
					this.Owner = null;
				}
			}
		}
	}
/*------------------------------------------------------------------------------
	removePortOwnership()
	accept:
	perform:
	return:
	exceptions:
	comments:
------------------------------------------------------------------------------*/
	public void removePortOwnershipListener(CommPortOwnershipListener c)
	{
		if(debug) System.out.println("CommPortIdentifier:removePortOwnershipListener()");
		/* why is this called twice? */
		if(ownershipListener != null)
			ownershipListener.removeElement(c);
	}

/*------------------------------------------------------------------------------
	internalClosePort()
	accept:       None
	perform:      clean up the Ownership information and send the event
	return:       None
	exceptions:   None
	comments:     None
------------------------------------------------------------------------------*/
	void internalClosePort()
	{
		synchronized(this) {
			if(debug) System.out.println("CommPortIdentifier:internalClosePort()");
			Owner = null;
			Available = true;
			commport = null;
			/*  this tosses null pointer?? */
			notifyAll();
		}
		fireOwnershipEvent(CommPortOwnershipListener.PORT_UNOWNED);
	}
/*------------------------------------------------------------------------------
	fireOwnershipEvent()
	accept:
	perform:
	return:
	exceptions:
	comments:
------------------------------------------------------------------------------*/
	void fireOwnershipEvent(int eventType)
	{
		if(debug) System.out.println("CommPortIdentifier:fireOwnershipEvent( " + eventType + " )");
		if (ownershipListener != null)
		{
			CommPortOwnershipListener c;
			for ( Enumeration e = ownershipListener.elements();
				e.hasMoreElements();
				c.ownershipChange(eventType))
				c = (CommPortOwnershipListener) e.nextElement();
		}
	}
}

